/*
 * Copyright (c) 2025-Present
 * All rights reserved.
 *
 * This code is licensed under the BSD 3-Clause License.
 * See the LICENSE file for details.
 */

#include "include/mdict_extern.h"

// #include <sys/time.h>
// #include <unistd.h> // for getopt

#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <iostream>
#include <string>
using std::cout;
using std::endl;

#pragma once
/* 微型跨平台 getopt – 仅支持短选项 */
#ifdef _WIN32
#include <string.h>
#define optind __optind
#define opterr __opterr
#define optopt __optopt
#define optarg __optarg
int __optind = 1, __opterr = 1, __optopt = 0;
char* __optarg = nullptr;
int getopt(int argc, char* const argv[], const char* optstring);
#else
#include <unistd.h>  // 原生 getopt
#endif

#ifdef _WIN32

int getopt(int argc, char* const argv[], const char* optstring) {
    static int nextchar = 1;
    const char* pos;
    if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
        return -1;
    if (strcmp(argv[optind], "--") == 0) {
        ++optind;
        return -1;
    }

    optopt = argv[optind][nextchar++];
    pos = strchr(optstring, optopt);
    if (!pos || optopt == ':') {
        if (argv[optind][nextchar] == '\0') {
            ++optind;
            nextchar = 1;
        }
        return '?';
    }
    if (pos[1] == ':') {
        if (argv[optind][nextchar] != '\0')
            optarg = &argv[optind][nextchar];
        else if (++optind >= argc)
            return ':';
        else
            optarg = argv[optind];
        nextchar = 1;
        ++optind;
    } else {
        if (argv[optind][nextchar] == '\0') {
            ++optind;
            nextchar = 1;
        }
    }
    return optopt;
}
#endif

typedef long long int64;

// Helper function to check if a string ends with a specific suffix
bool ends_with(const std::string& str, const std::string& suffix) {
    if (str.length() < suffix.length()) {
        return false;
    }
    return str.compare(str.length() - suffix.length(), suffix.length(),
                       suffix) == 0;
}

class Timetool {
public:
    static int64_t getSystemTime() noexcept {
        using namespace std::chrono;
        // 获取系统时钟的当前时间点
        auto now_ms = time_point_cast<milliseconds>(system_clock::now());
        // 返回自 1970-01-01 00:00:00 UTC 起的毫秒数
        return now_ms.time_since_epoch().count();
    }
};

void for_each_key(void* dict, std::function<void(simple_key_item*)> on_key) {
    uint64_t len = 0;
    simple_key_item** list = mdict_keylist(dict, &len);
    std::cerr << "[for_each_key] got len=" << len << "\n";  // diagnostic

    if (!list || len == 0) {
        std::cerr << "[for_each_key] nothing to do\n";
        return;
    }

    for (unsigned long i = 0; i < len; ++i) {
        if (!list[i]) {
            std::cerr << "[for_each_key] skipping null at i=" << i << "\n";
            continue;
        }
        on_key(list[i]);
    }
    mdict_free_keylist(list, len);
}

void print_usage(const char* program_name) {
    std::cout
        << "Usage: " << program_name
        << " [options] <dictionary_file> [query_key]\n"
        << "Options:\n"
        << "  -l, --list        List all keys in the dictionary\n"
        << "  -h, --help        Display this help message\n"
        << "  -v, --verbose     Enable verbose output\n"
        << "  -x, --hex         Output in hex format for MDD files\n"
        << "  -n, --no-content  Only show definition existence and length\n"
        << "  -t, --timing      Show lookup/locate timing in milliseconds\n"
        << "\n"
        << "Examples:\n"
        << "  " << program_name
        << " dictionary.mdx word        # Look up a word\n"
        << "  " << program_name
        << " -l dictionary.mdx          # List all keys\n"
        << "  " << program_name
        << " -x dictionary.mdd image    # Get image in hex format\n"
        << "  " << program_name
        << " -n dictionary.mdx word     # Show only definition length\n";
}

bool is_mdd_file(const std::string& filename) {
    std::string ext;
    size_t dot_pos = filename.find_last_of('.');
    if (dot_pos != std::string::npos) {
        ext = filename.substr(dot_pos);
        std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
    }
    return ext == ".mdd";
}

int main(int argc, char** argv) {
    bool list_keys = false;
    bool verbose = false;
    bool hex_output = false;
    bool no_content = false;
    bool show_timing = false;
    int opt;

    std::string definition_hex;

    // Parse command line options
    while ((opt = getopt(argc, argv, "lhvxnt")) != -1) {
        switch (opt) {
            case 'l':
                list_keys = true;
                break;
            case 'v':
                verbose = true;
                break;
            case 'x':
                hex_output = true;
                break;
            case 'n':
                no_content = true;
                break;
            case 't':
                show_timing = true;
                break;
            case 'h':
                print_usage(argv[0]);
                return 0;
            default:
                print_usage(argv[0]);
                return 1;
        }
    }

    // Check remaining arguments
    if (optind >= argc) {
        std::cerr << "Error: Dictionary file is required\n";
        print_usage(argv[0]);
        return 1;
    }

    const char* dict_file = argv[optind++];
    const char* query_key = nullptr;

    // If not listing keys, require a query key
    if (!list_keys) {
        if (optind >= argc) {
            std::cerr << "Error: Query key is required when not listing keys\n";
            print_usage(argv[0]);
            return 1;
        }
        query_key = argv[optind];
    }

    if (verbose) {
        std::cout << "Dictionary file: " << dict_file << std::endl;
        if (query_key) {
            std::cout << "Query key: " << query_key << std::endl;
        }
    }

    int64 t1 = Timetool::getSystemTime();
    void* dict = mdict_init(dict_file);

    int64 t2 = Timetool::getSystemTime();
    if (verbose) {
        std::cout << "Init cost time: " << t2 - t1 << "ms" << std::endl;
    }

    bool is_mdd = is_mdd_file(dict_file);

    if (list_keys) {
        uint64_t key_list_len = 0;
        simple_key_item** key_list_result = mdict_keylist(dict, &key_list_len);

        if (key_list_len == 0) {
            std::cerr << "No keys in dictionary\n";
            mdict_destory(dict);
            return 1;
        }

        std::cout << "Total keys: " << key_list_len << "\n";
        if (verbose) {
            std::cout << "File type: " << (is_mdd ? "MDD" : "MDX") << "\n\n";
        }

        for (unsigned long i = 0; i < key_list_len; ++i) {
            simple_key_item* key = key_list_result[i];
            if (!key || !key->key_word) continue;

            std::string original_str = key->key_word;

            if (verbose) {
                std::cout << "<================ start key index :[" << i
                          << "] =================>\n";
                std::cout << "Original string: " << original_str << "\n";
                std::cout << (is_mdd ? "Decoded (MDD): " : "Key (MDX): ")
                          << original_str << "\n";
                std::cout << "<================   end key index :[" << i
                          << "] =================>\n";
            } else {
                std::cout << original_str << "\n";
            }
        }

        mdict_free_keylist(key_list_result, key_list_len);
    } else {
        // char** result = arr.data();
        char* result[2];
        int64 lookup_start = Timetool::getSystemTime();
        if (!is_mdd) {
            mdict_lookup(dict, query_key, result);
            // cout << *result << endl;
        } else {
            // For MDD files, use hex encoding if -x option is specified,
            // otherwise use base64
            mdict_encoding_t encoding =
                hex_output ? MDICT_ENCODING_HEX : MDICT_ENCODING_BASE64;
            mdict_locate(dict, query_key, result, encoding);
        }
        int64 lookup_end = Timetool::getSystemTime();

        if (show_timing) {
            std::cout << "Lookup/Locate time: " << (lookup_end - lookup_start)
                      << "ms" << std::endl;
        }

        std::string definition(*result);

        if (verbose) {
            std::cout << ">>[definition start] [" << query_key
                      << "] length: " << definition.length() << " >>"
                      << std::endl;
        }

        if (no_content) {
            if (definition.empty()) {
                std::cout << "-1" << std::endl;
            } else {
                std::cout << definition.length() << std::endl;
            }
        } else {
            std::string ext;
            const char* dot = strrchr(query_key, '.');
            if (dot && *(dot + 1)) {
                ext = dot + 1;
                std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
            }

            std::string mime_type = mime_detect(query_key);
            if (mime_type != "application/octet-stream") {
                if (is_mdd && hex_output) {
                    std::cout
                        << definition
                        << std::endl;  // Output raw hex for MDD with -x option
                } else {
                    std::cout << "data:" << mime_type << ";base64,"
                              << definition << std::endl;
                }
            } else {
                std::cout << "query key:" << query_key
                          << " | def:" << definition << std::endl;
            }
        }

        if (verbose) {
            std::cout << "<<[definition   end] [" << query_key << "]" << " <<"
                      << std::endl;
        }

        int64 t3 = Timetool::getSystemTime();
        if (verbose) {
            std::cout << "Lookup cost time: " << t3 - t2 << " ms" << std::endl;
        }
    }

    mdict_destory(dict);
    return 0;
}
